LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

Bonbon

Go farther and see the brighter light

stm32-usb-vpc-hs

2022/4/10

STM32-USB-VPC-HS

简介

USB(Universal Serial BUS),通用串行总线

由四根线VCC、GND、D+(DP)、D-(DM)组成

前两个是电源线,后两个是差分传输数据线。

Communication Devices Class(Virtual Port Com)虚拟串口是USB通信的一种方式

另外还有HID(Human Interface Device),例如你的鼠标键盘。

(如果要详细了解USB通信,推荐一个公众号–鱼鹰谈单片机)


电路实现

STM32内部可以实现全速OTG,只需要将DP、DM对应的引脚接到USB接口对应的引脚即可。

但是如果要实现更高速的高速OTG,,则需要外接高速PHY,并且接线也不同。

为了调试方便,一般会将USB的数据线经过芯片HS8836A(可以理解为拓展坞),引出多路输出,

一些用于调试,一些用于主要通信。


外部高速PHY可以选用USB3300-EZK-TR,

USB的DP、DM数据线作为它的输入,采用ULPI协议,

输出8个数据位和STP、DIR、CLK、NXT

ULPI协议的信号定义如下:

DATA:8-bit双向数据总线

NXT:next,当连接设备发送数据时,NXT=1代表被接受,当连接设备接受数据时,NXT=1总线上有数据

CLK:clock,时钟

DIR:direct,用于确定数据总线方向

STP:stop,用于停止PHY的输出


将上述的12个引脚分别接到STM32,

以STM32F405RGT6为例,参照手册,引脚对应如下

PC0(OTG_HS_ULPI_STP) ---- STP

PC2(OTG_HS_ULPI_DIR) ---- DIR

PC3(OTG_HS_ULPI_NXT) ---- NXT

PA5(OTG_HS_ULPI_CK) ---- CLK

PA3、PB0、PB1、PB10、PB11、PB12、PB13、PB5(OTG_HS_ULPI_D0~7) ---- DATA0~7


软件实现

采用STM32CubeMX生成代码,主要就是选择

Connectivity>USB_OTG_HS>Device_Only

Middleware>USB_DEVICE>Communication Devices Class(Virtual Port Com)

时钟保证48MHz


一般这样子将代码烧进单片机,电脑就可以识别到虚拟串口了

在设备管理器>端口(COM和LPT)>STMicorelectronics Virtual COM Port

我这边遇到了很多问题,简单记录一下

刚开始是USB设备描述符请求失败,估计是焊接的问题,

但是再加工板子的时候不小心刮掉了阻焊层,VCC和GND连到一起,锡去不掉,只能重新焊另一块板子

焊接完是电脑识别不到,Debug代码发现卡在死在初始化,一步步调试发现选择外部phy超时,

这个外部phy表面上看着焊接得很好,电表测过,怀疑是加热太久芯片坏了,换个新的焊接上去

然后代码不会卡在死循环,正常运行,但是仍然是识别不到,

检查芯片和外部phy的每一个引脚,并将有可能虚焊的引脚全部再加工,终于可以识别了


排错过程参考了一些博客,简单记录:

1.USB时钟配置是否48MHz

2.堆栈设置是否不合适,我都设置为0x800

3.是否没有安装驱动,可以参考:https://blog.csdn.net/weixin_42243510/article/details/88715350

4.是否用了拓展坞,试试直接连接电脑

5.以上都没问题的话,可能是硬件有问题。


回环测试

在生成的代码中找到usbd_cdc_if.c,找到CDC_Receive_HS,接收时会调用这个函数

我们让它把接收到的数据发送出去

添加CDC_Transmit_HS(Buf,*Len);

打开串口调试助手,查看发送是否能接收到一样的数据即可。

阅读全文

STM32CubeMX

2022/4/10

STM32CubeMX

STM32是之前学的,学的标准库版本

最近重新捡起才发现跟不上时代了

未来主流应该都是HAL库以及STM32CUBEMX代码生成器

而且生成代码可以直接省去驱动的移植

所以就要学习一下HAL库以及STM32CUBEMX


java环境安装

STM32CubeMX的安装需要java环境

安装链接:https://www.oracle.com/java/technologies/downloads

一键安装,只需要选择路径,连环境变量都配置好了

安装完后可以在命令行查看版本,确认是否安装完成

java --version

(我为了进一步确认,在JetBrains Toolbox下了IDEA,跑了个HelloWorld)


STM32CubeMX安装

安装链接:https://www.st.com/en/development-tools/stm32cubemx.html


简单使用

先安装HAL库,在help>Manage embedded software packages

选择ACCEE TO MCU SELECTOR ,选择芯片后start project

配置一个串口通信的例子

选择System Core>SYS>Serial Wire 保留烧录口

选择System Core>RCC>Crystal/Ceramic Resonator 外部晶振

选择Connectivity>USART1>Asynchronous 异步

配置时钟,外接晶振8MHZ


然后就是导出工程了,选择路径和工程名字,使用keil5所以选择IDE为MDK_ARM

Code Generator中,

选择Copy only the necessary files (编译较快)

选择Generate peripheral initialization as a pair of “.c/.h” files per peripheral (每个外设独立.c和.h文件)

然后就是GENERATE CODE一键生成!

阅读全文

socket

2022/4/10

socket通信

socket

socket 被译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。

通过 socket 约定,不同计算机之间可以进行通信

在linux里一切皆文件,socket是一个表示网络连接的文件,对应网络连接的文件描述符。

在windows里,socket是一个网络连接,有别于文件。


流格式套接字

流格式套接字也叫“面向连接的套接字”(Stream Sockets),在代码中使用 SOCK_STREAM 表示。

SOCK_STREAM 是一种可靠的、双向的通信数据流,

数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。

流格式套接字使用了 TCP 协议,TCP 协议会控制你的数据按照顺序到达并且没有错误。


TCP

TCP全称The Transmission Control Protocol,传输控制协议

各个标志位及其意义:SYN(建立连接标志位)、ACK(确认标志位)、FIN(释放连接标志位)

TCP建立客户端与服务端的连接需要经过三次握手

第一次:Client将SYN置1,并产生随机数seq=x,发送数据包,Server接受判断SYN为1

第二次:Server将SYN和ACK置1,ack=x+1,seq=y,发送数据包,Client判断ack为x+1,ACK为1

第三次:Client将ACK置1,ack=y+1,发送数据包,Server接受判断ack为y+1,ACK为1

上述过程完成即建立连接


TCP断开客户端与服务端的连接需要经过四次挥手

第一次挥手:Client将FIN置1,发送数据包

第二次挥手:Server接受判断FIN为1,将ACK置1,发送数据包

第三次挥手:Server做好了释放服连接准备,将FIN置1,发送数据包

第四次挥手:Client接受判断FIN为1,将ACK置1,发送数据包

上述过程完成即断开连接


代码实现

代码实现分为客户端(client)和服务端(server)的实现

C++实现

C语言中文网的例程非常清晰,可以直接参考使用

Windows参考:http://c.biancheng.net/view/2129.html

Linux参考:http://c.biancheng.net/view/2128.html


注意事项:

在windows下使用g++编译需要加上-lws2_32选项,例如编译server.cpp如下

g++ server.cpp -o server.exe -lws2_32

如果用该代码与调试助手通信,需要注意端口和地址格式

例如我拿sockit进行调试,地址格式为AF_INET,端口前需要加上htons


Qt实现

Qt支持跨平台,我在windows上实现的效果,可以运用到linux。

最终实现了client界面和server界面编程,并经测试两端可以互通。

client界面
#include "clientwindow.h"
#include "serverwindow.h"

ClientWindow::ClientWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // 由于linux qt版本较低
    // connect 采用旧版写法
    // 即 connect(Button,&QPushButton::clicked,this,&ButtonClicked);
    // 写成 connect(Button,SIGNAL(clicked()),this,SLOT(ButtonClicked()));

    //模式选择
    ChooseClientButton = new QPushButton(this);
    ChooseClientButton->setText("客户端");
    ChooseClientButton->move(0,0);
    ChooseServerButton = new QPushButton(this);
    ChooseServerButton->setText("服务端");
    ChooseServerButton->move(100,0);
    connect(ChooseServerButton,SIGNAL(clicked()),this,SLOT(ChooseServerButtonClicked())); //本身是客户端 所以只绑定服务端按钮

    //Ip输入框
    IpLabel = new QLabel(this);
    IpLabel->setText("Ip:");
    IpLabel->move(70,50);
    IpInput = new QLineEdit(this);
    IpInput->setFixedSize(200,35);
    IpInput->move(100,50);
    IpInput->setPlaceholderText("Ip地址...");
    IpInput->setClearButtonEnabled(true);

    //端口输入端
    PortLabel = new QLabel(this);
    PortLabel->setText("Port:");
    PortLabel->move(50,100);
    PortInput = new QLineEdit(this);
    PortInput->setFixedSize(200,35);
    PortInput->move(100,100);
    PortInput->setPlaceholderText("端口...");
    PortInput->setClearButtonEnabled(true);

    //连接按钮
    ConnectButton = new QPushButton(this);
    ConnectButton->setText("连接");
    ConnectButton->move(50,150);
    connect(ConnectButton,SIGNAL(clicked()),this,SLOT(ConnectButtonClicked()));
    connected = false;

    //断开按钮
    DisconnectButton = new QPushButton(this);
    DisconnectButton->setText("断开");
    DisconnectButton->move(190,150);
    connect(DisconnectButton,SIGNAL(clicked()),this,SLOT(DisconnectButtonClicked()));

    //文本输入框
    TextInput = new QTextEdit(this);
    TextInput->move(50,230);
    TextInput->setFixedSize(250,300);
    TextInputLabel = new QLabel(this);
    TextInputLabel->move(50,200);
    TextInputLabel->setText("发送内容");

    //发送按钮
    SendButton = new QPushButton(this);
    SendButton->move(50,550);
    SendButton->setText("发送");
    SendButton->setFixedSize(250,35);
    connect(SendButton,SIGNAL(clicked()),this,SLOT(SendButtonClicked()));

    //发送记录
    RecordLabel = new QLabel(this);
    RecordLabel->move(550,50);
    RecordLabel->setText("发送记录");
    RecordText = new QTextEdit(this);
    RecordText->move(350,80);
    RecordText->setFixedSize(500,450);
    RecordText->setReadOnly(true);

    //记录所有发送文本
    AllRecordText = QString();

    //client
    socket = new QTcpSocket();

    //清除按钮
    ClearRecordTextButton = new QPushButton(this);
    ClearRecordTextButton->move(350,550);
    ClearRecordTextButton->setText("清空");
    ClearRecordTextButton->setFixedSize(500,35);
    connect(ClearRecordTextButton,SIGNAL(clicked()),this,SLOT(ClearRecordTextButtonClicked()));

}

ClientWindow::~ClientWindow()
{


}


void ClientWindow::ConnectButtonClicked()
{
    //获取ip和端口
    Ip = IpInput->text();
    Port = PortInput->text().toInt();

    //连接
    socket->connectToHost(Ip, Port);
    if(socket->waitForConnected(1000))
    {
        connected = true;
        AllRecordText += "已连接\n";
        IpInput->setReadOnly(true);
        PortInput->setReadOnly(true);
        connect(socket,SIGNAL(readyRead()),this,SLOT(ReadServerData()));// 兼容
    }
    else
    {
        AllRecordText += "连接失败,请重新连接\n";
    }
    RecordText->setText(AllRecordText);


}

void ClientWindow::DisconnectButtonClicked()
{
    //断开连接
    connected = false;
    socket->disconnectFromHost();
    IpInput->setReadOnly(false);
    PortInput->setReadOnly(false);
}

void ClientWindow::SendButtonClicked()
{
    if(connected)
    {
        //多行文本 要按换行符分割 可以用QString::SkipEmptyParts跳过空行
        QStringList TextInputList = \
                TextInput->toPlainText().split(QRegExp("[\r\n]"),QString::SkipEmptyParts);
        for(int i =0;i<TextInputList.size();i++)
        {
            CurrentTime = QDateTime::currentDateTime();
            QString current_date =CurrentTime.toString("hh:mm:ss");

            TextInputList[i] +='\n';
            AllRecordText += "[" + current_date + "] " + \
                             Ip + ":" + QString::number(Port) + ": " + \
                    "-->: " + TextInputList[i];
            socket->write(TextInputList[i].toUtf8());
        }
        RecordText->setText(AllRecordText);
    }
    else
    {
        AllRecordText += "未连接,请连接\n";
        RecordText->setText(AllRecordText);
    }
}

void ClientWindow::ClearRecordTextButtonClicked()
{
    //清空记录
    AllRecordText = QString();
    RecordText->setText(AllRecordText);
}

void ClientWindow::ChooseServerButtonClicked()
{

    ServerWindow *server_window = new ServerWindow;
    server_window->resize(900,600);
    server_window->setFont(QFont("宋体",8));
    server_window->setWindowTitle("Socket TCP 通信");
    this->deleteLater();
    this->close();
    server_window->move(this->pos().x(),this->pos().y());
    server_window->show();

}
void ClientWindow::ReadServerData()
{
    QString ReadData = socket->readAll();
    //多行文本 要按换行符分割 可以用QString::SkipEmptyParts跳过空行
    QStringList TextInputList = \
            ReadData.split(QRegExp("[\r\n]"),QString::SkipEmptyParts);
    for(int i =0;i<TextInputList.size();i++)
    {
        CurrentTime = QDateTime::currentDateTime();
        QString current_date =CurrentTime.toString("hh:mm:ss");

        TextInputList[i] +='\n';
        AllRecordText += "[" + current_date + "] " + \
                         Ip + ":" + QString::number(Port) + ": " + \
                        "<--: " + TextInputList[i];
    }
    RecordText->setText(AllRecordText);
}

效果如下:


server界面

server和client实现类似

#include "serverwindow.h"
#include "clientwindow.h"

ServerWindow::ServerWindow(QWidget *parent)
    : QMainWindow(parent)
{
    //由于linux qt版本较低
    //connect 采用旧版写法
    //即 connect(Button,&QPushButton::clicked,this,&ButtonClicked);
    //写成 connect(Button,SIGNAL(clicked()),this,SLOT(ButtonClicked()));

    //模式选择
    ChooseClientButton = new QPushButton(this);
    ChooseClientButton->setText("客户端");
    ChooseClientButton->move(0,0);
    ChooseServerButton = new QPushButton(this);
    ChooseServerButton->setText("服务端");
    ChooseServerButton->move(100,0);
    connect(ChooseClientButton,SIGNAL(clicked()),this,SLOT(ChooseClientButtonClicked())); //本身是服务端 所以只绑定客户端按钮

    //Ip输入框
    IpLabel = new QLabel(this);
    IpLabel->setText("Ip:");
    IpLabel->move(70,50);
    IpInput = new QLineEdit(this);
    IpInput->setFixedSize(200,35);
    IpInput->move(100,50);
    IpInput->setPlaceholderText("Ip地址...");
    IpInput->setClearButtonEnabled(true);

    //端口输入端
    PortLabel = new QLabel(this);
    PortLabel->setText("Port:");
    PortLabel->move(50,100);
    PortInput = new QLineEdit(this);
    PortInput->setFixedSize(200,35);
    PortInput->move(100,100);
    PortInput->setPlaceholderText("端口...");
    PortInput->setClearButtonEnabled(true);

    //侦听按钮
    ListenButton = new QPushButton(this);
    ListenButton->setText("侦听");
    ListenButton->move(50,150);
    connect(ListenButton,SIGNAL(clicked()),this,SLOT(ListenButtonClicked()));
    listened = false;
    connected = false;

    //取消按钮
    DisListenButton = new QPushButton(this);
    DisListenButton->setText("取消侦听");
    DisListenButton->move(190,150);
    connect(DisListenButton,SIGNAL(clicked()),this,SLOT(DisListenButtonClicked()));

    //文本输入框
    TextInput = new QTextEdit(this);
    TextInput->move(50,230);
    TextInput->setFixedSize(250,300);
    TextInputLabel = new QLabel(this);
    TextInputLabel->move(50,200);
    TextInputLabel->setText("发送内容");

    //发送按钮
    SendButton = new QPushButton(this);
    SendButton->move(50,550);
    SendButton->setText("发送");
    SendButton->setFixedSize(250,35);
    connect(SendButton,SIGNAL(clicked()),this,SLOT(SendButtonClicked()));

    //发送记录
    RecordLabel = new QLabel(this);
    RecordLabel->move(550,50);
    RecordLabel->setText("发送记录");
    RecordText = new QTextEdit(this);
    RecordText->move(350,80);
    RecordText->setFixedSize(500,450);
    RecordText->setReadOnly(true);

    //记录所有发送文本
    AllRecordText = QString();

    //清除按钮
    ClearRecordTextButton = new QPushButton(this);
    ClearRecordTextButton->move(350,550);
    ClearRecordTextButton->setText("清空");
    ClearRecordTextButton->setFixedSize(500,35);
    connect(ClearRecordTextButton,SIGNAL(clicked()),
            this,SLOT(ClearRecordTextButtonClicked()));

}

ServerWindow::~ServerWindow()
{


}


void ServerWindow::ListenButtonClicked()
{
    //获取ip和端口
    Ip = IpInput->text();
    Port = PortInput->text().toInt();

    //进入侦听模式
    server = new QTcpServer(this);
    server->listen(QHostAddress(Ip),Port);
    connect(server,SIGNAL(newConnection()),this,SLOT(NewClientConnect()));
    listened = true;

    //显示侦听
    AllRecordText += "侦听中...\n";
    ListenButton->setEnabled(false);

    //禁止输入
    IpInput->setReadOnly(true);
    PortInput->setReadOnly(true);
    RecordText->setText(AllRecordText);

}

void ServerWindow::ClearRecordTextButtonClicked()
{
    //清空记录
    AllRecordText = QString();
    RecordText->setText(AllRecordText);
}

void ServerWindow::ChooseClientButtonClicked()
{
    //关闭服务端并在同位置显示客户端
    ClientWindow *client_window = new ClientWindow;
    client_window->resize(900,600);
    client_window->setFont(QFont("宋体",8));
    client_window->setWindowTitle("Socket TCP 通信");
    this->deleteLater();
    this->close();
    client_window->move(this->pos().x(),this->pos().y());
    client_window->show();
}

void ServerWindow::NewClientConnect()
{
    //显示
    AllRecordText += "已连接\n";
    RecordText->setText(AllRecordText);
    connected = true;

    //获得套接字并绑定
    socket = new QTcpSocket(this);
    socket = server->nextPendingConnection();
    connect(socket,SIGNAL(readyRead()),this,SLOT(ReadClientData()));
}

void ServerWindow::ReadClientData()
{
    //读取文本 按换行符分割 用QString::SkipEmptyParts跳过空行
    QString ReadData =  socket->readAll();
    QStringList TextInputList = ReadData.split(QRegExp("[\r\n]"),QString::SkipEmptyParts);

    //遍历取出显示内容
    for(int i =0;i<TextInputList.size();i++)
    {
        CurrentTime = QDateTime::currentDateTime();
        QString current_date =CurrentTime.toString("hh:mm:ss");

        TextInputList[i] +='\n';
        AllRecordText += "[" + current_date + "] " + \
                Ip + ":" + QString::number(Port) + ": " + \
                "<--: " + TextInputList[i];
    }

    //显示
    RecordText->setText(AllRecordText);
}

void ServerWindow::DisListenButtonClicked()
{
    if(listened)
    {
        //显示
        AllRecordText += "取消侦听\n";
        RecordText->setText(AllRecordText);

        //取消侦听 close是禁止连接 还是可以通信
        server->deleteLater();
        ListenButton->setEnabled(true);
        IpInput->setReadOnly(false);
        PortInput->setReadOnly(false);
        listened = false;
        connected = false;
    }
}

void ServerWindow::SendButtonClicked()
{
    if(connected)
    {
        //发送文本 要按换行符分割 可以用QString::SkipEmptyParts跳过空行
        QStringList TextInputList = TextInput->toPlainText().split(
                    QRegExp("[\r\n]"),QString::SkipEmptyParts);
        for(int i =0;i<TextInputList.size();i++)
        {
            CurrentTime = QDateTime::currentDateTime();
            QString current_date =CurrentTime.toString("hh:mm:ss");

            TextInputList[i] +='\n';
            AllRecordText += "[" + current_date + "] " + \
                    Ip + ":" + QString::number(Port) + ": " + \
                    "-->: " + TextInputList[i];
            socket->write(TextInputList[i].toUtf8());
        }
        RecordText->setText(AllRecordText);
    }
    else
    {
        AllRecordText += "未连接,请连接\n";
        RecordText->setText(AllRecordText);
    }
}


效果如下:



程序打包

为了让程序可以在多个平台上使用

我将qt程序分别在windows和linux编译成可执行文件,并打包依赖环境

windows打包环境采用qt自带的windeployqt,比较简单

linux打包环境采用需要自己编写脚本文件,实现如下:

1.打包依赖项

编写了一个pack.sh用于打包依赖环境,内容如下

#!/bin/sh
dir=$(pwd) # current dir
liblist=$(ldd $1 | awk '{if (match($3,"/")){ printf("%s ",$3)}}') # get lib list
cp $liblist $dir # copy

通过ldd命令显示依赖环境,结合awk,match匹配获得动态链接库的绝对路径

最后通过cp命令将这些文件拷贝到当前文件夹、

2.运行时指定动态库链接路径

如果更换了一台电脑,它并不会默认当前文件夹为动态链接库搜索路径

所以需要一个与可执行文件同名的的脚本,内容如下

#!/bin/sh
appname=$(basename $0 | cut -d . -f1) 
dirname=$(dirname $0)
LD_LIBRARY_PATH=$dirname
export LD_LIBRARY_PATH
chmod +x $dirname/$appname
$dirname/$appname

程序实现的也就是设置动态链接库为该目录

所以在其他机器上只要运行这个socket.sh即可将可执行文件链接该文件夹的动态链接库并成功执行

3.打包成App

为了获得更加方便的可点击执行的可执行文件,我还编写了getDesktop.sh脚本,内容如下

exe="socketTCP"
file_name=$exe".desktop"
echo "#!/usr/bin/env xdg-open" > $file_name
echo "[Desktop Entry]" >> $file_name
echo "Version=1.0" >> $file_name
echo "Type=Application" >> $file_name
echo "Terminal=false" >> $file_name
echo "Exec="$(pwd)"/"$exe".sh" >> $file_name
echo "Name="$exe >> $file_name
chmod +x $file_name

实现原理就是根据所在路径编写可点击执行的.desktop文件

阅读全文

semantic_segmentation

2022/4/10

语义分割

训练过自己数据集的语义分割模型

但是都是用的别人现成的代码

这一次试一下从头搭一下


数据集制作

之前有自己标过目标检测的数据集,用的工具是labelImg,挺好用的

这一次标语义分割的数据集,用的labelme

在一个目录下,创建imgs文件夹存放原始图片,创建jsons文件夹存放输出

然后在该目录下执行

labelme imgs --output jsons --nodata --autosave

简单来讲就是标注imgs的图片输出到jsons中 然后jsons的输出没有包含imgs的数据 自动保存

具体含义可以通过labelme -h获得

使用ctrl+N就可以开始标注了

如果标注位置相似,例如连续视频帧

可以用ctrl+shift+D,将当前标注的复制到下一张,然后微调即可


标完之后我们只有jsons文件

我们需要通过jsons文件获得marks

labelme自带了labelme_json_to_dataset

可以通过

labelme_json_to_dataset 00000.json --out marks

将00000.json对应的图片转成dataset到marks

生成的文件包括原图片、红色marks、原图和红色marks融合、label.txt文件

但是他有个缺点,就是只能针对一个文件,而且我需要的是二值化白色marks

他的实现也很简单,存放在labelme/cli/json_to_dataset.py中

我根据自己的需求将他的代码修改如下

import argparse
import base64
import json
import os
import os.path as osp
from labelme import utils

import numpy as np
import cv2 as cv


def main():
    # python json_to_dataset.py jsons --out masks
    # 命令行参数解析
    parser = argparse.ArgumentParser()
    parser.add_argument("json_file")
    parser.add_argument("-o", "--out", default=None)
    args = parser.parse_args()

    json_dir = args.json_file  # json存放目录
    file_list = os.listdir(json_dir)  # 所有json文件
    for file in file_list:
        index = os.path.splitext(file)[0]  # 文件名 用于后续保存
        json_file = os.path.join(json_dir, file)  # json路径

        # 判断目录是否存在
        out_dir = args.out
        if not osp.exists(args.out):
            os.mkdir(args.out)

        # 获取img
        data = json.load(open(json_file))
        image_path = os.path.join(os.path.dirname(json_file), data["imagePath"])
        with open(image_path, "rb") as f:
            image_data = f.read()
            image_data = base64.b64encode(image_data).decode("utf-8")
        img = utils.img_b64_to_arr(image_data)

        # 获取label
        label_name_to_value = {"_background_": 0, 'line': 1}
        lbl, _ = utils.shapes_to_label(
            img.shape, data["shapes"], label_name_to_value
        ) #不同版本可能这里返回值要写成 lbl= 而不是 lbl, _ =

        # 转为二值化图像并保存
        label = np.array(lbl)
        label[np.where(label != 0)] = 255
        save_path = os.path.join(out_dir, index + ".png")
        cv.imwrite(save_path, label)

        print(json_file, '  ->  ', save_path)  # 打印查看进度


if __name__ == "__main__":
    main()

基本是用他的思路 删去不需要的内容 最终读写速度也是比较快的

然后就可以直接对整个文件夹的jsons提取黑白mask到masks文件夹

python json_to_dataset.py jsons --out masks


接下来记录一些比较需要注意的点

图像预处理

用pytorch做计算机视觉经常会接触到Numpy和PIL图像转换,实现如下:

img =Image.fromarray(img) # numpy转PIL
img =np.array(img) # PIL转numpy

但是使用时要注意,Image库读入的时RGB图像,Opencv库读入的时BGR图像


其实仔细一想,numpy可以转为tensor,opencv读入的也是numpy格式

用numpy处理矩阵数据,转换成tensor训练,本来是不需要经过PIL图像的

但是因为预处理常常会用到torchvision里的transfroms,这里的输入是PIL图像

为了避免类型频繁转换,我手动实现了预处理,这样子全过程都不涉及PIL图像


例如下列这个transforms就可以改成

img_transforms = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.ToTensor(), 
])

就可以改成

def normalized(data):
    data_min = np.min(data)
    data_max = np.max(data)

    if data_max - data_min != 0:
        return (data - data_min) / (data_max - data_min)
    else:
        if data_max != 0:
            return data / data_max
        else:
            return data
        
img = np.transpose(cv.resize(img, (256, 256)), (2, 0, 1))  # resize 和 通道交换
img_tensor = torch.Tensor(normalized(img))

transpose的用法 :原本是 (w,h,c)对应(0,1,2),需要转成(c,w,h)所以是(2,0,1)



Opencv的索引

因为经常会用到 但是又很容易忘记,记录一下

height, width = output.shape[:2]  # shape前两个元素是高和宽
img[row][col]  # 图像索引是行和列
cv.circle(show_img, (col, row), 3, (0, 0, 255))  # circle 是列和行
阅读全文

A-star

2022/4/10

二维网格A* 算法

记录一下二维网格A*算法实现的知识以及流程


预备知识

A*算法选用原则,选用综合优先级f(n)f(n)值较小的节点

f(n)=g(n)+h(n)f(n)=g(n)+h(n)

其中

g(n)g(n)表示从上一个父节点移动到指定点的移动代价,比如可以设直走为10,斜走为14,或者根据实际地形确定

h(n)h(n)表示从指定点到终点的估算成本,它是一个猜测,所以计算时不考虑中间障碍物

h(n)h(n)也叫做A*算法中的启发函数

常见有曼哈顿距离,对角距离,欧几里得距离

分别对应横竖走,横竖斜走以及任意方向走

设当前位置为(x,y)(x,y),目标位置为(xt,yt)(x_t,y_t),走一步的估算成本为D,则计算公式为

曼哈顿: h(n)=D(xnxt+ynyt)h(n) =D*( \vert x_n-x_t \vert + \vert y_n-y_t \vert)

对角:h(n)=D(xnxt+ynyt+(22)min(xnxt,ynyt))h(n)=D*(\vert x_n-x_t \vert + \vert y_n-y_t \vert + (\sqrt{2}-2)*min(\vert x_n-x_t \vert , \vert y_n-y_t \vert))

欧几里得:h(n)=D(xxt)2+(yyt)2h(n)=D*\sqrt{({x-x_t})^2+({y-y_t})^2}


步骤

这里我把步骤列出来,里面的一些细节后文补充

准备一个open_list和close_list,open_list是待遍历的节点,close_list是已遍历的节点

设定一个起点和终点,每一个网格都要记录父节点,f(n)f(n)g(n)g(n)h(n)h(n)


流程:

将起点加入open_list

循环:

	遍历open_list,查找f(n)值最小的节点,作为当前要处理的节点,并把这个节点移到close_list中

	对于当前节点附近的8个方格,剔除掉不可抵达的或者在close_list中的,作如下操作:

		把不在open_list的加入open_list,并把当前节点设为他们的父节点,计算f(n),g(n),h(n)

		如果他在open_list,检查这条路径:

			如果路径更好,把节点的父节点设为当前节点,重新计算f(n),g(n),h(n)

			如果路径不是更好,则不做处理

	判断终止条件:

		如果终点加入了open_list,则找到路径,从终点不断找父节点回到终点

补充:

1.在刚开始,open_list中只有起点,所以起点也就是要处理的节点

2.计算g(n)g(n)要累加,表示起点到当前节点nn的代价和

3.检查路径是为了找出更好的路径,检查的依据是g(n)g(n),值越小路径越好。

举个例子就是从中心点A走到B然后再走到C,对比直接从A斜走到C

如果你觉得前者路径更好,则需要把C的父节点设为B,并重新计算它的f(n)f(n)g(n)g(n)

如果你觉得后者路径更好,则不需要做任何操作,因为C的父节点本来就是中心点A

[ABC]\left[ \begin{matrix} * & * & * \\ * & A & B \\ * & * & C \\ \end{matrix} \right]


代码实现

为了验证算法,我用符号简单搭了个二维地图,即省去调试图形化界面的时间,看起来也比较简便

import random
import operator
from math import sqrt


class Node:
    def __init__(self, x, y):
        self.display = 'o'
        self.father_node = None
        self.x = x
        self.y = y
        self.fn = 0
        self.gn = 0
        self.hn = 0


class MyMap:

    def __init__(self):
        # o 表示可走位置 * 表示起点 # 表示终点 @表示障碍物
        self.map_size = (10, 15)  # 高 宽
        self.game_map = [[Node(y, x) for x in range(self.map_size[1])] for y in range(self.map_size[0])]

        # 生成起点和终点 保证起点终点离得有半个地图远
        self.start_point = (0, 0)  # 行 列
        self.end_point = (0, 0)
        self.end_point = self.get_random_point(
            (int(self.map_size[0] / 2), int(self.map_size[1] / 2)),
            self.map_size)
        self.set_object_to_map(self.start_point, "*")
        self.set_object_to_map(self.end_point, "#")

        # 生成障碍物
        self.obstacle = []
        for _ in range(40):
            obstacle_point = self.get_random_point((0, 0), self.map_size)
            if not operator.eq(obstacle_point, self.start_point):
                if not operator.eq(obstacle_point, self.end_point):
                    self.obstacle.append(obstacle_point)
                    self.set_object_to_map(obstacle_point, "@")

    @staticmethod
    def get_random_point(bottom_left, top_right):
        return random.randint(bottom_left[0], top_right[0] - 1), \
               random.randint(bottom_left[1], top_right[1] - 1)

    def set_object_to_map(self, point, obj):
        self.game_map[point[0]][point[1]].display = obj

    def __repr__(self):
        display = ''
        for i in range(self.map_size[0]):
            for j in range(self.map_size[1]):
                display += self.game_map[i][j].display + '  '
            display += '\n'
        return display


def calc_manhattan_distance(start_point, end_point):
    return abs(start_point[0] - end_point[0]) + abs(start_point[1] - end_point[1])


def calc_diagonal_distance(start_point, end_point):
    x_abs = abs(start_point[0] - end_point[0])
    y_abs = abs(start_point[1] - end_point[1])
    return x_abs + y_abs + (sqrt(2) - 2) * min(x_abs, y_abs)


def calc_euclidean_distance(start_point, end_point):
    return sqrt((start_point[0] - end_point[0]) ** 2 + (start_point[1] - end_point[1]) ** 2)


def main():
    my_map = MyMap()
    open_list = []
    close_list = []
    go_straight_cost = 10  # 直走成本
    go_diagonally_cost = 14  # 斜走成本 sqrt(2)
    one_step_cost = 10  # 走一步的成本

    # 将起点加入open_list
    open_list.append(my_map.game_map[my_map.start_point[0]][my_map.start_point[1]])

    while True:
        if len(open_list) <= 0:
            print("没找到路径!\n")
            break

        # 根据fn排序选出当前节点
        open_list = sorted(open_list, key=lambda x: x.fn)
        now_node = open_list[0]

        # 如果选到终点就退出
        if operator.eq((now_node.x, now_node.y), my_map.end_point):
            node = now_node
            # 不断取父节点 原点的父节点是None
            while True:
                node = node.father_node
                if node:
                    if not operator.eq((node.x, node.y), my_map.start_point):  # 避免把原点覆盖
                        my_map.game_map[node.x][node.y].display = '-'
                else:
                    break
            break

        # 把当前节点从open_list移出到close_list
        for i, node in enumerate(open_list):
            if operator.eq((now_node.x, now_node.y), (node.x, node.y)):
                open_list.remove(open_list[i])
                break
        close_list.append(now_node)

        # 提取点位置 方便判断
        open_list_point = list(map(lambda m: (m.x, m.y), open_list))
        close_list_point = list(map(lambda m: (m.x, m.y), close_list))

        # 当前节点附近的八个位置
        for i in range(now_node.x - 1, now_node.x + 2):
            for j in range(now_node.y - 1, now_node.y + 2):
                if not operator.eq((now_node.x, now_node.y), (i, j)):
                    # 越界检查
                    if my_map.map_size[0] > i >= 0 and my_map.map_size[1] > j >= 0:
                        # 如果不是障碍物 也不在close_list中
                        if (i, j) not in my_map.obstacle and (i, j) not in close_list_point:
                            # 如果不在open_list中
                            if (i, j) not in open_list_point:
                                # 加入open_list并设置父节点
                                open_list.append(my_map.game_map[i][j])
                                my_map.game_map[i][j].father_node = now_node
                                # 计算gn
                                if i == now_node.x or j == now_node.y:
                                    my_map.game_map[i][j].gn = go_straight_cost  # 直走
                                else:
                                    my_map.game_map[i][j].gn = go_diagonally_cost  # 斜走
                                last_node = now_node.father_node
                                if last_node:
                                    my_map.game_map[i][j].gn += last_node.gn

                                # 计算hn
                                my_map.game_map[i][j].hn = \
                                    one_step_cost * calc_diagonal_distance((i, j), my_map.end_point)
                                my_map.game_map[i][j].fn = \
                                    my_map.game_map[i][j].gn + \
                                    my_map.game_map[i][j].hn

                            # 如果在open_list中
                            else:
                                # 计算当前路径gn
                                if i == now_node.x or j == now_node.y:
                                    my_map.game_map[i][j].gn = go_straight_cost
                                else:
                                    my_map.game_map[i][j].gn = go_diagonally_cost
                                last_node = now_node.father_node
                                my_map.game_map[i][j].gn += last_node.gn
                                now_path_gn = my_map.game_map[i][j].gn + now_node.gn

                                # 计算新路径gn
                                if i == last_node.x or j == last_node.y:
                                    new_path_gn = go_straight_cost
                                else:
                                    new_path_gn = go_diagonally_cost
                                new_path_gn += last_node.gn

                                # 判断是否更改指向和更新数据
                                if new_path_gn > now_path_gn:
                                    my_map.game_map[i][j].father_node = now_node
                                    my_map.game_map[i][j].gn = now_path_gn
                                    my_map.game_map[i][j].fn = \
                                        my_map.game_map[i][j].gn + \
                                        my_map.game_map[i][j].hn

    print(my_map)


if __name__ == "__main__":
    main()


效果如下:


参考:https://zhuanlan.zhihu.com/p/54510444


启发函数改进

f(n)=g(n)+h(n)f(n)=g(n)+h(n)

kk为结点nn走到目标结点的步数,则h(n)h(n)的作用如下:

如果h(n)=0h(n)=0,一定能找到最优路径

如果h(n)kh(n) \leqslant k,一定能找到最优路径,h(n)h(n)越小,拓展节点越多,时间越长

如果h(n)=kh(n) = k,一定能找到最优路径,且没有拓展无关节点,时间最短

如果h(n)kh(n) \geqslant k,不一定能找到最优路径,但拓展节点少,时间短


动态加权

开始时h(n)h(n)较大,到达目标点附近重要,拓展节点少,h(n)h(n)乘以较大系数

接近终点时h(n)h(n)较小,找到最优路径重要,拓展节点多,h(n)h(n)乘以较小系数

可以采用根号动态加权,即

h(n)=h(n)h(n)h(n) = h(n) * \sqrt{h(n)}

内积

计算当前结点到目标节点向量起点到目标节点向量的内积cross,

h(n)=h(n)+0.001crossh(n) = h(n) +0.001*cross

内积越大,向量相似度越高,h(n)h(n)越大

阅读全文

loop_order

2022/3/14

循环顺序错误导致的无效遍历

记录一下一个编程坏习惯导致的错误


c++要记录一段代码的运行时间很简单

clock_t start = clock();
//执行的代码
double t = (double)(clock() - start) / CLOCKS_PER_SEC;
std::cout << "code runtime:" << t << "s" << std::endl;

今天在跑代码的时候就发现有一段代码要运行很久

是一个两层循环,遍历次数也不多,不应该跑这么慢的

大致框架如下

for (auto a : A)
{
    for (auto b : B)
    {
        c = fun1(b);
        fun2(a, c);
    }
}

不仔细看的话,还真看不出有什么问题

后面才发现fun1经过了很多次无效循环

内外循环顺序应该颠倒过来

像下面这样子

for (auto b : B)
{
    c = fun1(b);
    for (auto a : A)
    {
        fun2(a, c);
    }
}

以往除了遍历图片像素会想一想内外循环的顺序,其他都是没怎么注意

以后遇到多层循环要留个心眼,最先用到的变量对应的循环应该考虑放最外面

当然上面只是一个例子,并不适用于所有情况

阅读全文

vector_and_list

2022/3/10

Vector 和 List

vector和list都是标准库中的容器

vector是动态数组,拥有一段连续的内存空间,优点在于可以用下标访问,访问效率高。但也正是因为这个原因,导致它在删除和插入时效率不高。

list是双向链表,内存空间是不连续的,所以访问效率不高,但是对于链表来说,插入和删除只需要移动指针指向,故它的删除和插入效率高。


最近项目开发有用到,这里记录一下一些需要注意的问题


删除元素

vector和list删除元素的方法都一样,这里以list为例

由于删除过程中有可能涉及迭代失效,这里记录一下

删除主要是用到erase,他的返回值是当前被删除元素的下一个元素的迭代器

所以我们可以在删除元素的时候顺便用其返回值赋给迭代器

没有删除元素时迭代器需要自增,删除元素时迭代器不需要自增


假设我们的需求是在容器中删除值为2,3,5的数,我们可以这样写

std::list<int> list1{1,2,2,2,3,4,5,6,7};

for(auto iter = list1.begin();iter!=list1.end();)
{
    if(*iter == 2 || *iter == 3 || *iter == 5)
    {
        iter = list1.erase(iter);
        continue;
    }
    else
    {
        iter ++;
    }        
}

但是如果有多个判断 而且判断条件很长的话 这样子写很不优雅 所以可以写成这样

std::list<int> list1{1,2,2,2,3,4,5,6,7};

for(auto iter = list1.begin();iter!=list1.end();)
{
    if(*iter == 2)
    {
        iter = list1.erase(iter);
        continue;
    }

    if(*iter == 3)
    {
        iter = list1.erase(iter);
        continue;
    }

    if(*iter == 5)
    {
        iter = list1.erase(iter);
        continue;
    }
    iter ++;
}

虽然看起来多此一举 但是我感觉这样写更有条理


遍历元素过程中往尾部插入元素

我们常常会需要往一个容器里加入元素,

对于vector和list来说,都可以使用push_back

但是如果不注意的话,很有可能导致死循环

即一边不断加入,一边又不断迭代下去

而最常规的想法就是在循环之前,就先记录下迭代的终点


假设我们的需求是迭代过程中不断往容器中加入数字2

并打印当前迭代处的元素(表示我们能访问到)

但是只循环原容器部分,即后面加入的数字2部分不循环


vector

vector最方便就是下标访问

很容易就可以实现

std::vector<int> vec1{1, 2, 3, 4, 5, 6, 7, 8};
int end_index = vec1.size();
for (int i = 0; i < end_index; i++)
{
    vec1.push_back(2);
    std::cout << vec1[i] << std::endl;
}

注意 vector不能使用迭代器边迭代边push_back

因为vector的扩容,是另外起一片内存,然后再把数据拷贝过去,

所以push_back之后迭代器位置会错误


list

list的话自然而然的想法就是用迭代器

std::list<int> list1{1, 2, 3, 4, 5, 6, 7, 8};
auto end_ptr = list1.end();
for (auto iter = list1.begin(); iter!=end_ptr; iter++)
{     
    list1.push_back(2);
    std::cout << *iter << std::endl;
}

但是上述代码会死循环

根据链表的性质,虽然end不变,但是随着你插入元素,你的节点越来越多,一直走不到end


所以要记录一下原来的容器的大小,限制迭代次数,如下

std::list<int> list1{1, 2, 3, 4, 5, 6, 7, 8};
int len = list1.size();
for (auto iter = list1.begin(); (len--)>0; iter++)
{     
    list1.push_back(2);
    std::cout << *iter << std::endl;
}
阅读全文

use_bibtex_compile_reference_in_vscode

2022/3/7

vscode中latex引用bibtex参考文献

制作.bib

新建一个ref.bib文件,到谷歌学术,找到自己想引用的论文,点击引用,选择Bibtex,将内容复制进去。

第一行@article{ 后面带的是名字,可以修改,我这里改成article_name


.tex文件引用

在main.tex中使用

\bibliographystyle{plain} % 这里也可以改成自己模板的style
\bibliography{ref} %对应ref.bib

引用处只需要

\cite{article_name}

编译

方案一:

先正常用latex编译生成.aux文件

然后命令行用bibtex编译

bibtex main

再次重新编译即可看到结果


方案二:

如果对vscode比较熟悉的,可以配置setting.json

找到latex插件对应的setting.json,修改编译方案,即可一键完成上述功能

"latex-workshop.latex.recipes": [
    {
        "name": "pdf->bib->pdf->pdf",
        "tools": [
            "pdflatex",
            "bibtex",
            "pdflatex",
            "pdflatex"
        ]
    }
],

使用以下宏包就可以有颜色且有超链接功能

\usepackage[colorlinks,linkcolor=red,anchorcolor=blue,citecolor=green]{hyperref}


参考:https://blog.csdn.net/sinat_36301420/article/details/79334728

参考:https://zhuanlan.zhihu.com/p/114733612

参考:https://blog.csdn.net/JohnJim0/article/details/103309475

阅读全文

config_compile_cpp_by_cmake_in_vscode

2022/2/26

在vscode中配置使用cmake编译c++

cmake简介

最常规的编译cpp文件就是通过g++ ,编译执行一个cpp文件可以简单通过

g++ main.cpp && ./a.out

但是当项目越来越大,编写的cpp文件越来越多,需要使用库等需求越来越多时,

用g++就显得没有那么方便,所以便引出了cmake


简单来讲,我们需要编写一个CMakeLists.txt来制定编译规则

然后cmake通过CMakeLists.txt指定的规则编译生成Makefile

然后我们通过make就可以生成可执行文件

假设CMakeLists.txt与main.cpp在同个目录下

事实上我们也一般会放在同个目录下

可以通过如下命令生成可执行文件

cmake . && make

这里cmake命令后面跟了一个点,表示CMakeLists.txt在当前路径


但是因为它会产生很多编译中间文件,我们是不需要使用到的

而我们也不希望这些文件把我们的文件夹搞得很乱

所以我们常常会新建一个build文件夹

然后让编译产生的中间文件以及可执行文件都放在里面

mkdir build && cd build
cmake .. && make

这里cmake命令后面跟了两个点,表示上一级路径

因为我们已经切换到build路径了

CMakeLists.txt相当于在build的上一个路径


使用cmake重点还是编写CMakeLists.txt

注意这个文件名大小写和字母一个都不能错

以使用opencv库为例介绍一下如何编写

具体还需要根据自己项目编写适合的

cmake_minimum_required(VERSION 2.8) # 版本
project(hello_opencv) # 项目名
set(CMAKE_CXX_STANDARD 11) #指定标准 当初没有指定C++11 使用标准库中的一些函数 编译不通过
set(CMAKE_BUILD_TYPE "Debug")# 设置build模式 有Debug和Release

# 使用opencv
find_package( OpenCV REQUIRED ) # 寻找OpenCV包 REQUIRED表示找不到就停止cmake
include_directories(${OpenCV_INCLUDE_DIRS}) # 添加头文件目录

add_executable(${PROJECT_NAME} # ${PROJECT_NAME} 表示项目名称
	main.cpp
)  # 指定编译生成的可执行文件的名称 以及源文件
target_link_libraries(${PROJECT_NAME}
	${OpenCV_LIBS} 
) # 指定可执行文件的名称 以及库 自定义库也可以通过add_libary设置

常常我们也可以set一个变量如THIRD_PARTY_LIBRARY,然后通过${THIRD_PARTY_LIBRARY}引用

由于只需要生成一个可执行文件

上面我把项目名称的名字作为可执行文件的名字

要生成多个可执行文件就分别指定



在vscode里使用cmake

vscode是我最喜欢的编辑器,下面介绍一下如何在vscode里用cmake编译cpp文件

下载拓展

在vscode拓展商店下载c++、cmake拓展


创建项目文件夹

vscode是以一个文件夹来进行管理的

创建一个文件夹,作为你的项目文件夹

将其拖入vscode


配置c_cpp_properties.json

ctrl+shift+p 输入C/C++ 选择配置编辑json 将生成默认c_cpp_properties.json文件

修改编译器路径为g++ 修改c和c++的版本 添加Cmaketools拓展配置信息

修改后文件内容如下

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
            ],
            "defines": [],
            "compilerPath": "/usr/bin/g++",
            "cStandard": "c11",
            "cppStandard": "c++98",
            "intelliSenseMode": "gcc-x64",
            "configurationProvider": "ms-vscode.cmake-tools"
        }
    ],
    "version": 4
}

配置launch.json文件

点击vscode左侧Run运行 选择C++(GDB/LLDB) 点击创建launch.json文件

修改项目名称为无后缀文件名

修改后文件内容如下

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 启动",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/${fileBasenameNoExtension}",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

配置tasks.json

ctrl+shift+p 输入task 选择Tasks:Run Task

选择第一个没有配置的任务…配置任务

选择使用模板创建tasks.json文件

选择Others、选择配置tasks

加入cmake编译的命令

修改后文件内容如下

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "make build",
            "type": "shell",
            "command": "cd ./build ;cmake .. ;make",
            "group": {
                "kind": "build",
                "isDefault": true
            }

        },
        {
            "label": "clean",
            "type": "shell",
            "command": "make clean"
        }
    ]
}

编辑CMakeLists.txt

如果只是简单编译main.cpp 可以编辑如下

cmake_minimum_required(VERSION 2.8)
project(hello) 
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE "Debug")

add_executable(${PROJECT_NAME}
	main.cpp
)

测试

配置完最好重启一下vscode,它才会在最下方显示cmake编译、调试、运行的按钮

写个最简单的hello_world程序测试

main.cpp

#include <iostream>

int main(void)
{
    std::cout<<"hello world"<<std::endl;
	return 0;
}

点击运行,有显示hello world即配置成功

阅读全文

clean_up_memory_on_ubuntu

2022/2/24

清ubuntu内存

首先跟着读三遍

不用autoremove!不用autoremove!不用autoremove!

非常庆幸在刚入门linux的时候就看到一篇用了autoremove直接系统崩溃的文章

所以在那之后我看到autoremove都是直接略过

autoremove有可能会误删一些系统文件,导致系统无法正常运行


今天系统提醒我说内存不够了要清清了,我想着也是,用了好久都没清过

先查看内存使用情况

df -h

发现不仅/home满了,好多/dev/loop也满了


loop满了是正常现象,如果觉得查看碍眼可以在输出的时候排除squashfs文件系统

df -x squashfs -h

参考:https://www.cnblogs.com/raina/p/12613164.html


清/home的话就因人而异了,基本就是把没用的大文件删掉,把下载里的压缩包删掉等等

下面记录一下删除文件并清空回收站后内存仍然不变的情况

回收站路径对应~/.local/share/Trash

里面有三个文件夹expunged、files、info

files里面是删除了放回收站的文件、info是信息

expunged是一些你删除的文件中的一些不属于你的文件

所以会导致你在桌面清空了回收站剩余内存却没有增加的清况

这里我把它清空

cd ~/.local/share/Trash/expunged
sudo rm -rf * # 注意这里是强制删除该目录下的所有文件 一定要确保切换到正确路径
阅读全文